/*
 * PSquareGrid.java
 *
 * 2d rectangular grid of crypts.
 * von Neuman neighborhood
 * Note: wrapping not implemented correctly yet!!!
 * cyclic boundary on x axis only
 */

package sim;

import java.util.*;
import lib.KnuthRandom;

public class PSquareGrid extends PGrid {
    private int _nXSize,_nYSize;
    private Boolean _bWrapX;  // cyclic boundary on X axis
    private Boolean _bWrapY;  // cyclic boundary on Y axis
    private KnuthRandom _r;

    public PSquareGrid(int nXSize, int nYSize, CryptFactory factory, TissueEnvironmentSettings params, boolean bWrapX, boolean bWrapY, long iRandomSeed) {
	super();
	_r = new KnuthRandom();
	_r.seedRandom(iRandomSeed);

	_nXSize = nXSize;
	_nYSize = nYSize;
	_bWrapX = bWrapX;
	_bWrapY = bWrapY;
	_crypts = new Crypt[_nXSize*_nYSize];
	for (int i=0; i<_crypts.length; i++) {
	    if(params.getCryptMode()==TissueEnvironmentSettings.CryptMode.Diversity){
                _crypts[i] = factory.createDiversityCrypt();
            } else if(params.getCryptMode()==TissueEnvironmentSettings.CryptMode.Space){
                _crypts[i] = factory.createSpaceCrypt();
            } else if(params.getCryptMode()==TissueEnvironmentSettings.CryptMode.Real){
                _crypts[i] = factory.createRealCrypt();
            }
	    _crypts[i].setGrid(this);
	    _crypts[i].setIndex(i);
	    _crypts[i].setParams(params);
	}
	for (int i=0; i<_crypts.length; i++) {
	    ArrayList n = getNeighbors(i);
	    for (int k=0; k<n.size(); k++) {
		Integer c = (Integer)n.get(k);
		_crypts[i].addNeighbor(_crypts[c.intValue()]);
	    }
	}
    }
    public double getX(int n) { return n%_nXSize; }
    public double getY(int n) { return n/_nXSize; }
    public double getMaxX() { return _nXSize-1; }
    public double getMaxY() { return _nYSize-1; }
    public boolean simulationIsComplete(long lCurTimestep) { return false; }

    public int getWidth() {
        return _nXSize;
    }

    public int getHeight() {
        return _nYSize;
    }



    /* getNeighbors
     * returns all grid cells touching crypt n
     */
    public ArrayList<Integer> getNeighbors(int n) {
	ArrayList<Integer> neighbors = new ArrayList<Integer>();
	int x = n%_nXSize;
	int y = n/_nXSize;
	for (int i=-1; i<=1; i++) {
	    for (int j=-1; j<=1; j++) {
		if ((i!=0 || j!=0) && j+y>=0 && j+y<_nYSize) {
		    //        if (i==0 ^ j==0) {
		    int a = (x+i+_nXSize)%_nXSize + (y+j)*_nXSize;
		    neighbors.add(new Integer(a));
		}
	    }
	}
	return neighbors;
    }


    /* 
     * Sets random neighbors of crypt n, 
     * k neighbors at distance r from crypt n
     */
    public void setRandomCryptNeighborsWithinRadius(int n, int k, int r) {
	ArrayList<Integer> neighbors = new ArrayList<Integer>(k);
	// int _nXSize = getWidth();
	// int _nYSize = getHeight();
	int x = n%_nXSize;
	int y = n/_nXSize;
	int rx, ry, rrx, rry;
	if (!_bWrapX && !_bWrapY) {
	    for(int kk=0;kk<k;kk++){
		do{
		    rrx = _r.randomInt(2*r);
		    rry = _r.randomInt(2*r);		    
		    rx = Math.min(Math.max(x+rrx-r,0),_nXSize);		
		    ry = Math.min(Math.max(y+rry-r,0),_nYSize);		
		} while (Math.hypot((rx-x),(ry-y))>r);
		neighbors.add(new Integer(rx+ry*_nXSize));	    
	    }
	} else if (_bWrapX && !_bWrapY) {
	    for(int kk=0;kk<k;kk++){
		do{
		    rrx = _r.randomInt(2*r);
		    rry = _r.randomInt(2*r);		    
		    rx = Math.min(Math.max(x+rrx-r,0),_nXSize);		
		    ry = Math.min(Math.max(y+rry-r,0),_nYSize);		
		} while (Math.hypot((rx-x),(ry-y))>r && Math.hypot((x+_nXSize-rx),(ry-y))>r);
		neighbors.add(new Integer(rx+ry*_nXSize));	    
	    }
	} else if (_bWrapX && _bWrapY) {
	    for(int kk=0;kk<k;kk++){
		do{
		    rrx = _r.randomInt(2*r);
		    rry = _r.randomInt(2*r);		    
		    rx = Math.min(Math.max(x+rrx-r,0),_nXSize);		
		    ry = Math.min(Math.max(y+rry-r,0),_nYSize);		
		} while (Math.hypot((rx-x),(ry-y))>r && 
			 Math.hypot((x+_nXSize-rx),(ry-y))>r && 
			 Math.hypot((x+_nXSize-rx),(y+_nYSize-ry))>r &&
			 Math.hypot((rx-x),(y+_nYSize-ry))>r);
		neighbors.add(new Integer(rx+ry*_nXSize));	    
	    }
	}
	_crypts[n]._neighbors = new ArrayList<Crypt>(k);
	for (int kk=0; kk<k; kk++) {
	    Integer c = (Integer)neighbors.get(kk);
	    _crypts[n].addNeighbor(_crypts[c.intValue()]);
	}
    }


    public Crypt getCryptAtCenter() {
	return getCrypt(_nXSize/2 + (_nYSize/2)*_nXSize);
    }
}
